using System;
using System.Xml;
using System.Text;
using System.Globalization;

namespace Waymex.Gps
{
	/// <summary>
	/// This object represents a collection of Waypoint objects.
	/// </summary>
	/// <remarks>
	/// <para>
	/// When receiving data from the Host, most GPS devices will overwrite any existing 
	/// waypoints with the same name stored in the GPS device. For example, if the Host 
	/// sends a waypoint named CBELLS, most GPS devices will overwrite a waypoint named
	/// CBELLS that may be stored in GPS memory.
	/// </para>
	/// <para>
	/// However some GPS devices compare the position of the new waypoint with the position
	/// of the stored waypoint (altitude is ignored). If the positions match, the GPS
	/// will erase the identically named waypoint and replace it with the new one.
	/// If the positions differ, the unit will create a new, unique name for the
	/// incoming waypoint and preserve the existing waypoint under its original name.
	/// </para>
	/// <para>
	/// When the Host requests the GPS to send waypoints, the GPS will send every
	/// waypoint stored in its database. When the Host sends waypoints to the GPS,
	/// the Host may selectively transfer any number of waypoints.
	/// </para>
	/// <para>
	/// When the Host requests the GPS to send proximity waypoints, the GPS will send all proximity
	/// waypoints stored in its database.
	/// </para>
	/// </remarks>
	public class WaypointCollection : System.Collections.IEnumerable
	{
					
		//error constant
		private const string ERR_MSG_4000 = "Function not available in Evaluation Version";
		private const string XML_ROOT = "gpswaypoints";
		private const string XML_WAYPOINT = "gpswaypoint";

		private int m_hashCode = -1;

		private System.Collections.ArrayList mcWayPoints; 
		
		/// <summary>
		/// Constructor.
		/// </summary>
		public WaypointCollection() : base()
		{
			mcWayPoints = new System.Collections.ArrayList();

		}
		/// <summary>
		/// Allows a Waypoint Object to be added to the collection. 
		/// </summary>
		public void Add(Waypoint waypoint)
		{
			try
			{
				mcWayPoints.Add(waypoint);
			}
			catch (Exception e)
			{
				throw new InvalidDataException(e.Message, e);
			}
		}
		/// <summary>
		/// Returns the first Waypoint object that matches the specified Identifier.
		/// </summary>
        /// <param name="identifier"></param>
		/// <returns></returns>
		public Waypoint Find(string identifier)
		{
			Waypoint result = null;
 
			foreach(Waypoint objWaypoint in mcWayPoints)
			{
				if( objWaypoint.Identifier.ToUpper(CultureInfo.InvariantCulture) == identifier.ToUpper(CultureInfo.InvariantCulture) )
					result = objWaypoint;
			}

			return result;
		}
		/// <summary>
		/// Returns the number of Waypoint objects in the collection.
		/// </summary>
		public int Count
		{
			get
			{
				try
				{
					return mcWayPoints.Count;
				}

				catch (Exception e)
				{
					throw new InvalidDataException(e.Message, e);
				}
			}
		}

		/// <summary>
		/// Can be used to refer to a member of the collection by ordinal reference.
		/// This Property is the default indexer property in C#.
		/// </summary>
		public Waypoint this[int index]
		{
			get
			{
				return (Waypoint)mcWayPoints[index];
			}

		}
		/// <summary>
		/// Returns an Enumerator for the Collection.
		/// </summary>
		public System.Collections.IEnumerator GetEnumerator()
		{
			return mcWayPoints.GetEnumerator();
		}
	
		/// <summary>
		/// Returns an XML representation of the object.
		/// </summary>
        public string ToXml()
        {
            return ToXml(String.Empty);
        }
        /// <summary>
        /// Returns an XML representation of the object.
        /// This overloaded method accepts an xslt filename which can be used to ransform the xml.
        /// </summary>
        public string ToXml(string xsltFilename)
		{
			StringBuilder strbldXML = new StringBuilder("");
            String xml = String.Empty;

			try
			{
				strbldXML.Append ("<");
				strbldXML.Append (XML_ROOT);
				strbldXML.Append (">");

				foreach(Waypoint objWaypoint in mcWayPoints)
				{
					strbldXML.Append(objWaypoint.ToXml());
				}

				strbldXML.Append ("</");
				strbldXML.Append (XML_ROOT);
				strbldXML.Append (">");

                //transform if required
                xml = strbldXML.ToString();

                if (xsltFilename.Length > 0)
                {
                    xml = Waymex.Xml.Transform.XsltTransform(xml, xsltFilename);
                }            
            }

            catch (NullReferenceException e)
            { 
				throw new XmlException(e.Message, e);

			}

            return xml;
		}

		/// <summary>
		/// This method populates the object from XML.
		/// </summary>
        public void XmlLoad(string xml)
		{

			XmlDocument objDOM = new XmlDocument();

			try
			{
                objDOM.LoadXml(xml);

				if(objDOM.FirstChild.Name == XML_ROOT)
				{
					foreach(XmlNode objNode in objDOM.FirstChild.ChildNodes)
					{
						try
						{
							switch(objNode.Name)
							{
								case XML_WAYPOINT:

									Waypoint objWaypoint = new Waypoint();
									objWaypoint.XmlLoad(objNode.OuterXml);
									mcWayPoints.Add(objWaypoint);
									break;
							
							}
						}
						catch(NullReferenceException ex)
						{
						}
					}
				}	
			}
            catch (NullReferenceException e)
            {
				throw new XmlException(e.Message, e);
			}
		}
		private static string ByteToHex(byte[] ByteData)
		{
			//---------------------------------------------------------------------------
			//	DESCRIPTION:	This function converts an array of bytes to a string
			//					of hex digits representing those bytes.
			//
			//
			//	PARAMETERS:
			//				ByteData()  Array of bytes.
			//
			//	RETURNS:
			//				String      String of hex character pairs representing
			//							each byte in the byte array, separated
			//							with a space.
			//
			//---------------------------------------------------------------------------
		
			string sTemp = "";
			string sMessage = "";

				if(ByteData != null)
				{
					for(int f = 0; f < ByteData.Length; f++)
					{
						sTemp = ByteData[f].ToString("X"); //hex returns a string representing the passed number

						if(sTemp.Length < 2)
						{
							sTemp = string.Concat("0", sTemp);
						}
						
						sMessage = string.Concat(sMessage, " ", sTemp);
					}
					
					return sMessage.Trim();
				}
				else
				{
					return "";
				}
		}

		/// <summary>
		/// This method returns a System.Data.Dataset populated with the contents of the WaypointCollection objec.
		/// The dataset includes one table called 'Waypoints'.
		/// </summary>
		public System.Data.DataSet ToDataSet()
		{
			//constants for the dataset
			const string DS_WAYPOINTS = "Waypoints";

			//constants for the waypoints table
			const string DS_TABLE_WAYPOINTS = "Waypoints";
			const string DS_FIELD_WAYPOINT_IDENTIFIER = "Identifier";
			const string DS_FIELD_WAYPOINT_IDENTIFIER_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_LATITUDE = "Latitude";
			const string DS_FIELD_WAYPOINT_LATITUDE_TYPE = "System.Double";
			const string DS_FIELD_WAYPOINT_LONGITUDE = "Longitude";
			const string DS_FIELD_WAYPOINT_LONGITUDE_TYPE = "System.Double";
			const string DS_FIELD_WAYPOINT_COMMENT = "Comment";
			const string DS_FIELD_WAYPOINT_COMMENT_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_SYMBOL = "Symbol";
			const string DS_FIELD_WAYPOINT_SYMBOL_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_ICON = "Icon";
			const string DS_FIELD_WAYPOINT_ICON_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_COLOUR = "Colour";
			const string DS_FIELD_WAYPOINT_COLOUR_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_DISPLAY = "Display";
			const string DS_FIELD_WAYPOINT_DISPLAY_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_PROX_DIST = "Proximity Distance";
			const string DS_FIELD_WAYPOINT_PROX_DIST_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_PROX_INDEX = "Proximity Index";
			const string DS_FIELD_WAYPOINT_PROX_INDEX_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_ALTITUDE = "Altitude";
			const string DS_FIELD_WAYPOINT_ALTITUDE_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_DEPTH = "Depth";
			const string DS_FIELD_WAYPOINT_DEPTH_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_CLASS = "Class";
			const string DS_FIELD_WAYPOINT_CLASS_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_SUB_CLASS = "Sub Class";
			const string DS_FIELD_WAYPOINT_SUB_CLASS_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_ATTRIBUTES = "Attributes";
			const string DS_FIELD_WAYPOINT_ATTRIBUTES_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_LINK = "Link";
			const string DS_FIELD_WAYPOINT_LINK_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_STATE = "State";
			const string DS_FIELD_WAYPOINT_STATE_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_COUNTRY = "Country";
			const string DS_FIELD_WAYPOINT_COUNTRY_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_CITY = "City";
			const string DS_FIELD_WAYPOINT_CITY_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_ADDRESS = "Address";
			const string DS_FIELD_WAYPOINT_ADDRESS_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_FACILITY = "Facility";
			const string DS_FIELD_WAYPOINT_FACILITY_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_CROSS_ROAD = "Cross Road";
			const string DS_FIELD_WAYPOINT_CROSS_ROAD_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_UNUSED_1 = "Unused 1";
			const string DS_FIELD_WAYPOINT_UNUSED_1_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_UNUSED_2 = "Unused 2";
			const string DS_FIELD_WAYPOINT_UNUSED_2_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_PACKET_TYPE = "Packet Type";
			const string DS_FIELD_WAYPOINT_PACKET_TYPE_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_TIME_EN_ROUTE = "Time En Route";
			const string DS_FIELD_WAYPOINT_TIME_EN_ROUTE_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_TEMPERATURE = "Temperature";
			const string DS_FIELD_WAYPOINT_TEMPERATURE_TYPE = "System.Single";
			const string DS_FIELD_WAYPOINT_CATEGORY = "Category";
			const string DS_FIELD_WAYPOINT_CATEGORY_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_TIMESTAMP = "Time Stamp";
			const string DS_FIELD_WAYPOINT_TIMESTAMP_TYPE = "System.Int32";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS = "Route Link Class";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS_TYPE = "System.Int16";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_ID = "Route Link Identifier";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_ID_TYPE = "System.String";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS = "Route Link Sub Class";
			const string DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS_TYPE = "System.String";

			//create a new data set
			System.Data.DataSet ds = new System.Data.DataSet(DS_WAYPOINTS);
            ds.Locale = CultureInfo.InvariantCulture;
					
			//create the waypoints table			
			System.Data.DataTable dtW = new System.Data.DataTable(DS_TABLE_WAYPOINTS);
            dtW.Locale = CultureInfo.InvariantCulture;

			//add the columns to the waypoints table
			dtW.Columns.Add(DS_FIELD_WAYPOINT_IDENTIFIER, Type.GetType(DS_FIELD_WAYPOINT_IDENTIFIER_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_LATITUDE, Type.GetType(DS_FIELD_WAYPOINT_LATITUDE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_LONGITUDE, Type.GetType(DS_FIELD_WAYPOINT_LONGITUDE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_COMMENT , Type.GetType(DS_FIELD_WAYPOINT_COMMENT_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_SYMBOL, Type.GetType(DS_FIELD_WAYPOINT_SYMBOL_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ICON, Type.GetType(DS_FIELD_WAYPOINT_ICON_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_COLOUR, Type.GetType(DS_FIELD_WAYPOINT_COLOUR_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_DISPLAY, Type.GetType(DS_FIELD_WAYPOINT_DISPLAY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_PROX_DIST, Type.GetType(DS_FIELD_WAYPOINT_PROX_DIST_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_PROX_INDEX, Type.GetType(DS_FIELD_WAYPOINT_PROX_INDEX_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ALTITUDE, Type.GetType(DS_FIELD_WAYPOINT_ALTITUDE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_DEPTH, Type.GetType(DS_FIELD_WAYPOINT_DEPTH_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CLASS, Type.GetType(DS_FIELD_WAYPOINT_CLASS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_SUB_CLASS, Type.GetType(DS_FIELD_WAYPOINT_SUB_CLASS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ATTRIBUTES, Type.GetType(DS_FIELD_WAYPOINT_ATTRIBUTES_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_LINK, Type.GetType(DS_FIELD_WAYPOINT_LINK_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_STATE, Type.GetType(DS_FIELD_WAYPOINT_STATE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_COUNTRY, Type.GetType(DS_FIELD_WAYPOINT_COUNTRY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CITY, Type.GetType(DS_FIELD_WAYPOINT_CITY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ADDRESS, Type.GetType(DS_FIELD_WAYPOINT_ADDRESS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_FACILITY, Type.GetType(DS_FIELD_WAYPOINT_FACILITY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CROSS_ROAD, Type.GetType(DS_FIELD_WAYPOINT_CROSS_ROAD_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_UNUSED_1, Type.GetType(DS_FIELD_WAYPOINT_UNUSED_1_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_UNUSED_2, Type.GetType(DS_FIELD_WAYPOINT_UNUSED_2_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_PACKET_TYPE, Type.GetType(DS_FIELD_WAYPOINT_PACKET_TYPE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_TIME_EN_ROUTE, Type.GetType(DS_FIELD_WAYPOINT_TIME_EN_ROUTE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_TEMPERATURE, Type.GetType(DS_FIELD_WAYPOINT_TEMPERATURE_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_CATEGORY, Type.GetType(DS_FIELD_WAYPOINT_CATEGORY_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_TIMESTAMP, Type.GetType(DS_FIELD_WAYPOINT_TIMESTAMP_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ROUTE_LINK_ID, Type.GetType(DS_FIELD_WAYPOINT_ROUTE_LINK_ID_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS, Type.GetType(DS_FIELD_WAYPOINT_ROUTE_LINK_CLASS_TYPE));
			dtW.Columns.Add(DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS, Type.GetType(DS_FIELD_WAYPOINT_ROUTE_LINK_SUB_CLASS_TYPE));

			//add the data
			foreach(Waypoint objWaypoint in mcWayPoints)
			{
				object[] objRow = new object[32];
				objRow[0] = objWaypoint.Identifier;
				objRow[1] = objWaypoint.Latitude;
				objRow[2] = objWaypoint.Longitude;
				objRow[3] = objWaypoint.Comment;
				objRow[4] = objWaypoint.Symbol;
				objRow[5] = objWaypoint.Icon;
				objRow[6] = objWaypoint.Colour;
				objRow[7] = objWaypoint.Display;
				objRow[8] = objWaypoint.ProximityDistance;
				objRow[9] = objWaypoint.ProximityIndex;
				objRow[10] = objWaypoint.Altitude;
				objRow[11] = objWaypoint.Depth;
				objRow[12] = objWaypoint.Class;
				objRow[13] = objWaypoint.Subclass.ToString();				//byte array in Waypoint object
				objRow[14] = objWaypoint.Attributes;
				objRow[15] = objWaypoint.Link;
				objRow[16] = objWaypoint.State;
				objRow[17] = objWaypoint.Country;
				objRow[18] = objWaypoint.City;
				objRow[19] = objWaypoint.Address;
				objRow[20] = objWaypoint.Facility;
				objRow[21] = objWaypoint.Crossroad;
				objRow[22] = objWaypoint.Unused1;
				objRow[23] = objWaypoint.Unused2;
				objRow[24] = objWaypoint.PacketType;
				objRow[25] = objWaypoint.TimeENRoute;
				objRow[26] = objWaypoint.Temperature;
				objRow[27] = objWaypoint.Category;
				objRow[28] = objWaypoint.Timestamp;
				objRow[29] = objWaypoint.RouteLinkIdentifier;
				objRow[30] = objWaypoint.RouteLinkClass;
				objRow[31] = objWaypoint.RouteLinkSubclass.ToString();		//byte array in Waypoint object

				dtW.Rows.Add(objRow);
				
			}
			//tables populated so add the tables to the dataset
			ds.Tables.Add(dtW);

			return ds;
		}
        /// <summary>
        /// Overridden method. Returns true of the values of each
        /// of the properties are equal in value.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>boolean</returns>
        public override bool Equals(object obj)
		{
			return Equals(obj, false);
		}
        /// <summary>
        /// Overridden method. Returns true of the values of each
        /// of the properties are equal in value. Passing true
        /// to the positionOnly parameter will compare the Trackpoint
        /// in terms of the Latitude and Longitude only.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="positionOnly"></param>
        /// <returns>boolean</returns>
        public bool Equals(object obj, bool positionOnly)
		{
			try
			{
				WaypointCollection objWaypoints = null;

				//check the type first
				if(obj.GetType() != this.GetType() )
					return false;

				//type ok so cast to Waypoints
				objWaypoints = (WaypointCollection)obj;

				//if the number of waypoints in each collection is different then exit
				int w1Count = mcWayPoints.Count;
				int w2Count = objWaypoints.Count;
				if(w1Count != w2Count)
					return false;

				//both same type with the same number of elements so chech each one in turn
				for( int index = 0; index < w1Count; index++ )
				{
					if( !objWaypoints[index].Equals(mcWayPoints[index], positionOnly) )
					return false;
				}
			}
			catch
			{
				throw;
			}
			finally
			{}

			//if we got here then they must match
			return true;
		}
        /// <summary>
        /// Overridden function. Retrieves a value that indicates the hash code value for the object.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
		{
			//this value will be used for the overriden GetHashCode function and should
			//ensure that two object that are the same return the same Hash Code.
			//the hash code has to be imutable to is stored in a member variable.
			if( m_hashCode <= 0 )
				m_hashCode = this.ToXml().GetHashCode();
	
			return m_hashCode;
		}
	}

}
